package org.ccay.excel.importer.service.impl.internal;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;

import ognl.Ognl;
import ognl.OgnlException;
import ognl.OgnlOps;
import ognl.TypeConverter;

import org.ccay.core.log.ILogger;
import org.ccay.core.log.CcayLoggerFactory;
import org.ccay.core.util.StringUtil;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;


/**
 * 利用OGNL实现的VO转换器。
 * 
 * @author chaowangbang
 * @since Mar 22, 2012
 */
public class VOConverterByOgnl extends AbstractVOConverter {

	/** 表达式缓存 **/
	private ConcurrentHashMap<String, Object> expressions = new ConcurrentHashMap<String, Object>();
	
	private TypeConverter typeConverter = new TypeConverterImpl();

	private static final ILogger log = CcayLoggerFactory.getLogger(VOConverterByOgnl.class);
	
	/**
	 * Bean属性列表缓存
	 */
	private WeakHashMap<String, Set<String>> beanProperties = new WeakHashMap<String, Set<String>>();
	
	/**
	 * 
	 */
	public VOConverterByOgnl() {
	}
	
	/**
	 * @param voClass
	 */
	public VOConverterByOgnl(Class<?> voClass) {
		super(voClass);
	}

	/* (non-Javadoc)
	 * @see org.ccay.excel.importer.service.impl.internal.AbstractVOConverter#populateObject(java.lang.Object, java.util.Map)
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected void populateObject(Object instance,
			Map<String, Object> voProperties)throws ConvertException{
		Map context = Ognl.createDefaultContext(instance);
		context.put(ReflectionContextState.CREATE_NULL_OBJECTS, Boolean.TRUE);
		setProperties(voProperties, instance, context);
	}

	/** 设置属性
	 * @author chaowangbang
	 * @since Mar 20, 2012
	 * @param props
	 * @param o
	 * @param context
	 * @throws ConvertException 
	 */
	private void setProperties(Map<String, ?> props, Object o, Map<String, Object> context) throws ConvertException{
		if (props == null) {
			return;
		}
		// 设置类型转换器
		Ognl.setTypeConverter(context, typeConverter);

		Object oldRoot = Ognl.getRoot(context);
		Ognl.setRoot(context, o);
		
		Set<String> beanProperties = getBeanProperties(o.getClass());

		// 设置Bean的各属性值
		for (Map.Entry<String, ?> entry : props.entrySet()) {
			String expression = entry.getKey();
			if(StringUtil.isNullOrEmpty(expression)) {
				continue;
			}
			if(!(o instanceof Map) && (expression.indexOf('.')==-1 && !beanProperties.contains(expression))) {
				continue;
			}
			if(entry.getValue() != null){
				try {
					Ognl.setValue(compile(expression), context, o, entry.getValue());
				} catch (OgnlException e) {
					throw new ConvertException(e);
				}
			}
		}

		Ognl.setRoot(context, oldRoot);
	}
	
	/** 
	 * 获取Java类的Bean属性列表。
	 * 	
	 * @author chaowangbang
	 * @since 2012-7-6
	 * @param clazz
	 * @return
	 * @throws IntrospectionException
	 */
	private Set<String> getBeanProperties(Class<?> clazz) {
		String key = clazz.getName();
		Set<String> properties = beanProperties.get(key);
		if(properties != null) {
			return properties;
		} else {
			properties = new HashSet<String>();
			
			try {
				PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(clazz).getPropertyDescriptors();
				for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
					properties.add(propertyDescriptor.getName());
				}
			} catch (IntrospectionException ex) {
				log.warn(ex);
			}
			
			beanProperties.put(key, properties);
			
			return properties;
		}
	}
	
	/** 编译EL表达式	
	 * @author chaowangbang
	 * @since Mar 20, 2012
	 * @param expression
	 * @return
	 * @throws OgnlException
	 */
	private Object compile(String expression) throws OgnlException {
		Object o = expressions.get(expression);
		if (o == null) {
			o = Ognl.parseExpression(expression);
			expressions.put(expression, o);
		}

		return o;

	}
	
	/**
	 * 类型转换器。
	 * 
	 * @author chaowangbang
	 *
	 */
	class TypeConverterImpl implements TypeConverter {
		
		/**
		 * 根据上下文信息，将指定的值转换为指定的数据类型。
		 * 
		 * @param context 上下文
		 * @param value	待转换的值
		 * @param toType	目标类型
		 * @return	目标类型的值
		 */
		public Object convertValue(Map context, Object value, Class toType)
	    {
			if(value == null){
				return null;
			}
			// 是否数值类型，空字符串无法转换
			if(Number.class.isAssignableFrom(toType)){
				if("".equals(value)){
					return null;
				}
			}
			// 是否是日期类型
			if(Date.class.isAssignableFrom(toType)) {
				return doConvertToDate(context, value, toType);
			}
			
	        return OgnlOps.convertValue(value, toType);
	    }

		/* (non-Javadoc)
		 * @see ognl.TypeConverter#convertValue(java.util.Map, java.lang.Object, java.lang.reflect.Member, java.lang.String, java.lang.Object, java.lang.Class)
		 */
		public Object convertValue(Map context, Object target, Member member,
				String propertyName, Object value, Class toType) {
			return convertValue(context, value, toType);
		}
		
		/**
		 * 将指定的值转换为日期类型。
		 * 
		 * @param context
		 * @param value
		 * @param toType
		 * @return
		 */
		private Object doConvertToDate(Map<String, Object> context, Object value, Class toType) {
	        Date result = null;
	        
	        // 待转换的值是字符串类型
	        if (value instanceof String && ((String) value).length() > 0) {
	            String sa = (String) value;
	            // locale
	            Locale locale = getLocale(context);

	            // 日期格式
	            DateFormat df = getDateFormat(sa, toType, locale);
	            
	            try {
	                df.setLenient(false); // let's use strict parsing (XW-341)
	                result = df.parse(sa);
	                if (!(Date.class == toType)) {
	                    try {
	                        Constructor constructor = toType.getConstructor(new Class[]{long.class});
	                        return constructor.newInstance(new Object[]{Long.valueOf(result.getTime())});
	                    } catch (Exception e) {
	                    	log.error(e);
	                        throw new TypeConvertException("Couldn't create class " + toType + " using default (long) constructor", e);
	                    }
	                }
	            } catch (ParseException e) {
	            	log.error(e);
	                throw new TypeConvertException("Could not parse date", e);
	            }
	        } else if (Date.class.isAssignableFrom(value.getClass())) { // 待转换的值本身就是日期类型
	            result = (Date) value;
	        }
	        return result;
	    }
		
		/**
		 * 获取日期格式
		 * 
		 * @param context
		 * @param value
		 * @param toType
		 * @param locale
		 * @return
		 */
		private DateFormat getDateFormat(String value, Class toType, Locale locale) {
			DateFormat df = null;
			
            if (java.sql.Time.class == toType) { // 目标类型是Time类型
                df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale);
            } else if (java.sql.Timestamp.class == toType) { // 目标类型是timestamp类型
                Date check = null;
                SimpleDateFormat dtfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT,
                        DateFormat.MEDIUM,
                        locale);
                SimpleDateFormat fullfmt = new SimpleDateFormat(dtfmt.toPattern() + ".SSS",
                        locale);

                SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT,
                        locale);

                SimpleDateFormat[] fmts = {fullfmt, dtfmt, dfmt};
                for (SimpleDateFormat fmt : fmts) {
                    try {
                        check = fmt.parse(value);
                        df = fmt;
                        if (check != null) {
                            break;
                        }
                    } catch (ParseException ignore) {
                    	log.debug("Fail to parse the date : [{0}] with pattern : [{1}] and locale : [{2}], but ignored.", value, fmt.toPattern(), locale);
                    	// log.error(ignore);
                    	// 忽略异常。原因：尝试利用多种日期格式来格式化日期值，格式化异常表示该格式不满足值的要求，因此没必要处理这种异常。
                    }
                }
            } else if (java.util.Date.class == toType) { // 目标类型是Date类型
            	Date now = new Date(); // for log
                Date check = null;
                DateFormat[] dfs = getDateFormats(locale);
                for (DateFormat df1 : dfs) {
                    try {
                        check = df1.parse(value);
                        df = df1;
                        if (check != null) {
                            break;
                        }
                    }
                    catch (ParseException ignore) {
                    	if(df1 instanceof SimpleDateFormat){
                    		SimpleDateFormat sdf = (SimpleDateFormat)df1; 
                    		log.debug("Fail to parse the date : [{0}] with pattern : [{1}] and locale : [{2}], but ignored.", value, sdf.toPattern(), locale);
                    	}else {
                    		log.debug("Fail to parse the date : [{0}] with pattern like : [{1}] and locale : [{2}], but ignored.", value, df1.format(now), locale);
						}
                    	// log.error(ignore);
                    	// 忽略异常。原因：尝试利用多种日期格式来格式化日期值，格式化异常表示该格式不满足值的要求，因此没必要处理这种异常。
                    }
                }
            }
            
            //final fallback for dates without time
            if (df == null) {
                df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
            }
            
            return df;
		}
		
		
		/**
		 * 获取位置信息
		 * 
		 * @param context
		 * @return
		 */
		private Locale getLocale(Map<String, Object> context) {
	        if (context == null) {
	            return Locale.getDefault();
	        }

	        Locale locale = (Locale) context.get("locale");

	        if (locale == null) {
	            locale = Locale.getDefault();
	        }

	        return locale;
	    }
		
		/**
		 * 获取日期格式列表
		 * 
		 * @param locale
		 * @return
		 */
		private DateFormat[] getDateFormats(Locale locale) {
	        DateFormat dt1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale);
	        DateFormat dt2 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale);
	        DateFormat dt3 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);

	        DateFormat d1 = DateFormat.getDateInstance(DateFormat.SHORT, locale);
	        DateFormat d2 = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
	        DateFormat d3 = DateFormat.getDateInstance(DateFormat.LONG, locale);

	        DateFormat rfc3399 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");

	        DateFormat[] dfs = {dt1, dt2, dt3, rfc3399, d1, d2, d3}; //added RFC 3339 date format (XW-473)
	        return dfs;
	    }
		
	}
	
	/**
	 * 获取对象的属性value
	 * @param o
	 * @param context
	 * @return
	 */
	@Override
	protected List<Object> getProperties(Object instance,String[] voProperties){
		List<Object> propertiesValue = new ArrayList<Object>(voProperties.length);
		//设置Bean的各属性值
		for (String str : voProperties) {
			if(StringUtil.isNullOrEmpty(str)){
				propertiesValue.add(null);
				continue;
			}
			try{
				propertiesValue.add(Ognl.getValue(str, instance));
			}catch (OgnlException e) {
				log.trace(" source is null " +e.getMessage());
				propertiesValue.add(null);
			}
		}
		return propertiesValue;
	}

}
